home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 5
/
Aminet 5 - March 1995.iso
/
Aminet
/
mus
/
edit
/
AlgoRhythms.lha
/
AlgoRhythms
/
Source
/
algorhythms.c
next >
Wrap
C/C++ Source or Header
|
1994-12-09
|
59KB
|
1,632 lines
/* AlgoRhythms.c
** Thomas E. Janzen 4 September 1989; 2-11-90
** 18 September 1989 26 nov 89 11 December 1989 16 December 1989
** Music played with this program is
** Copyright © 1990,1991,1992,1993,1993 Thomas E. Janzen
** The music is randomized but changes slowly by sinusoidal functions
** Copyright (c) © 1990, 1991, 1992, 1993 by Thomas E. Janzen
** All Rights Reserved
**
** THIS SOFTWARE IS FURNISHED FREE OF CHARGE FOR STUDY AND USE AND MAY
** BE COPIED ONLY FOR PERSONAL USE OR COMPLETELY AS OFFERED WITH NO
** CHANGES FOR FREE DISTRIBUTION. NO TITLE TO AND OWNERSHIP OF THE
** SOFTWARE IS HEREBY TRANSFERRED. THOMAS E. JANZEN ASSUMES NO
** RESPONSIBILITY FOR THE USE OR RELIABILITY OF THIS SOFTWARE.
**
** Thomas E. Janzen
** 208A Olde Derby Road
** Norwood, MA 02062-1761
** (617)769-7733
** tej@world.std.com
**
** FACILITY:
**
** AlgoRhythms music improviser on Commodore (TM) Amiga (TM)
** compiled with SAS/C Amiga Compiler 6.50
**
** ABSTRACT:
**
** AlgoRhythms.c improvises music over MIDI on a COMMODORE Amiga.
**
** AUTHORS: Thomas E. Janzen
**
** CREATION DATE: 26-MAR-1990
**
** MODIFICATION HISTORY:
** DATE NAME DESCRIPTION
1.01 12 Aug 90 T Janzen Shortened duration string to fit in new gadgets
1.02 8 Jan 91 Misc. improvements of code; added Events.nv_r_stop_time
1.03 11 feb 91 misc. and fixed bug of cut off notes on short scales
1.04 23 Feb 91 put back autorequesters using new compile command no crash
1.05 24 Jul 91 created a custom screen; added quartal and quintal scales;
1.06 24 SEP 91 use algorhythms.col to define colors, update.
1.07 3 NOV 91 Use struct timeval for all times until it must be double
1.08 10 NOV 91 TEJ integers in CHARACTER struct.290 notes/sec @ 16V,64 @ 1
2.0 26 DEC 91 TEJ Record MIDI and save MIDI standard file
Use req.library by Colin Fox and Bruce Dawson
Use 20 voices.
2.1 12 JAN 92 Integers for MaxNoteLen and MinNoteLen
2.2 17 JUL 92 Use AmigaDOS (TM) 2.04 ASL library if available.
5 SEP 92 Fix texture thickness calc to be 0 to 1 * range
15 SEP 92 turn off notes in all voices before turning on voices
Fix stuck notes in MUSICSERIAL
2.3 19 SEP 92 Reformat code
2.4 31 OCT 92 conform to SAS/C 6.0
2.5 23 FEB 93 interlace option/ 24 FEB 93 add workbench arguments
2.6 03 APR 93 Integrate internal audio and 8SVX instruments
2.7 14 OCT 93 used gadtools
2.8 22 OCT 93 added forms.c, voices.c with gadtools. Removed gadgets.c
removed redundant menus.
3.0 1 JAN 94 added colors events. General 3.0 Version additions.
26 NOV 94 fixed file drawer bug; released as Version 3.0
5 Dec 94 fix form path bug
3.1 10 December 94. Made it run even if serial device fails.
Improved serial device error reporting.
**
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <limits.h>
#include <intuition/intuition.h>
#include <exec/memory.h>
#include <exec/interrupts.h>
#include <exec/devices.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <proto/all.h>
#include <Workbench/startup.h>
#include <Libraries/asl.h> /* asl */
#include <proto/asl.h> /* asl */
#include <utility/tagitem.h>
#include <proto/req.h>
#include "AlgoRhythms.h"
#include "MusicTimer.h"
#include "MusicSerial.h"
#include "Files.h"
#include "Scales.h"
#include "audio.h"
#ifndef CLI
#include "Window.h"
#include "DrawForm.h"
#include "Menus.h"
#include "Record.h"
#include "Forms.h"
#include "Voices.h"
#include "musicrexx.h"
#include "colors.h"
#endif
#define TWOPI (2 * PI)
#define PROJECT (0)
#define FORM (1)
#define SCALE (2)
#define CHANNEL (3)
/*
** This type of structure holds the instantaneous values for the range and
** median pitch, dynamic, duration, and thickness.
*/
typedef struct character_struct CHARACTER_TYPE;
struct character_struct
{
int ch_i_pitch_range, /* range of pitches */
ch_i_pitch_mean, /* ~ median pitch */
ch_i_dynamic_range, /* range of dynamic levels */
ch_i_dynamic_mean, /* ~ median of dynamic levels */
ch_i_dur_range, /* range of durations */
ch_i_dur_mean, /* ~ median of durations */
ch_i_thickness; /* number of voices playing at once */
};
/*
** Inter-modular Globals: gi_fubar, scale, and gi_quit
** If the serial channel fails, gi_fubar is set and the program exits.
*/
int gi_fubar = FALSE,
gi_quit = FALSE; /* Was told to quit out of the program */
static int playing = FALSE, /* playing music */
started = FALSE, /* Has started playing music */
done = FALSE, /* done playing because it timed out*/
delay_ticks = 8, /* fiftieths of a second to wait */
range = 10, /* length of scale array */
half_range = 5, /* half of range (scale array len) */
num_voices = 4, /* */
scale[120] = {48,50,53,55,58,60,62,65,67,70,72,74,77};
static struct timeval range_time = {0, 0};
static double cvt_time_secs(struct timeval *Time);
static void stop_note( NOTE_EVENT_TYPE *new_event,
struct timeval *music_time);
static void ranges( const FORM_TYPE *form, const struct timeval *cur_time,
CHARACTER_TYPE *next_now, NOTE_LEN_TYPE *note_len);
static void make_event( NOTE_EVENT_TYPE *new_event, CHARACTER_TYPE *style,
struct timeval *music_time, const int event_index,
NOTE_LEN_TYPE *note_len);
static NOTE_EVENT_TYPE reset_event
= {{0, 0}, {0, 0}, {0, 0}, 0, 0, -1, 0, 0, 0, 0, 0};
const struct timeval zero_time = {0, 0};
static struct timeval stop_time; /* The Time that the music stopped */
#ifndef CLI
static void parse_menu( const int class, const int code,
struct timeval *str_time, FORM_TYPE *form,
struct timeval *duration, int *tempo,
NOTE_EVENT_TYPE *events, NOTE_LEN_TYPE *note_len);
#endif
int main(int argc, char *argv[]) /* CLI version */
/*
** FUNCTIONAL DESCRIPTION:
** AlgoRhythms main routine including Intuition maintenance loop
**
** ARGUMENTS: (only for CLI version)
**
** argc-
** description: count of arguments; only 2 allowed
** data_type: int
** access: read only
**
** argv-
** description: name of form file for CLI version only
** data_type: pointer to pointers to char
** access: read only
**
** DESIGN:
** ROUTINE main
** : open_wind()
** : open_midi_port()
** : IF midi port cannot be opened
** : : GOTO cleanup2
** : ENDIF
** : init_menu()
** : init temp, range, scale, and half_range.
** : get process pointer for ASL library
** : get process window pointer for ASL library
** : init voices
** : #if CLI
** : IF argument count was wrong
** : : put usage message
** : : GOTO cleanup2
** : ENDIF
** : copy argv[1] to file_string
** : read the specified form file
** : #else
** : randomize pitch, rhythm, dynamic, texture
** : IF workbench passed arguments
** : : IF arguments >= 1
** : : : get argument
** : : : IF argument is locked
** : : : : save current directory
** : : : : read form file from workbench argument
** : : : : restore directory
** : : : ENDIF
** : : ELSE
** : : : just read the form file from an unlocked workbench argument
** : : ENDIF
** : : set the window title from the workbench argument
** : ENDIF
** : #endif
** : IF file read in OK
** : : IF tempo = 0
** : : : init delay_ticks to 0
** : : ELSE
** : : : delay_ticks = 50/ tempo
** : : ENDIF
** : : half_range = range / 2
** : ENDIF
** : #endif
** : start_time()
** : put system time into sys_time
** : set pseudo-random number generator with time microseconds
** : start_time = sys_time
** : clear music_time
** : #ifndef CLI
** : draw_form()
** : #endif
** : cur_style = ranges
** : #if CLI
** : init started = TRUE, done = FALSE send_function(STARTFUNCT)
** : #endif
** : WHILE (not quitting)
** : : music_time = current time - start_time
** : : WHILE (playing = (!done AND started) AND !gi_quit)
** : : : music_time = current_time - start_time
** : : : IF it is time to update the instantaneous parameter ranges
** : : : : ranges(form, music_time, cur_style)
** : : : : range_time = music_time + 1/10 second
** : : : ENDIF
** : : : FOR event_index = 0 to num_voices
** : : : : IF music_time > the voice's stop_time
** : : : : : stop the note for that voice
** : : : : ENDIF
** : : : ENDFOR
** : : : FOR event_index = 0 to num_voices
** : : : : IF events[event_index] is not playing
** : : : : : make_event(events[event_index], cur_style, music_time)
** : : : : ENDIF
** : : : ENDFOR
** : : : #if not CLI
** : : : draw_time() (move time cursor on screen)
** : : : WHILE (message = GetMsg(w->UserPort) service Intuition windowing
** : : : : class = message->Class; code = message->Code
** : : : : ReplyMsg()
** : : : : IF class = MENUPICK AND code = MENUNULL
** : : : : : go to top of WHILE
** : : : : ENDIF
** : : : : parse_menu(class, code, start_time, form, piece_duration,
** tempo, events)
** : : : ENDWHILE
** : : : IF delay_ticks != 0
** : : : : FOR tick = 0 to delays_ticks BY 2
** : : : : : send MIDI clock byte
** : : : : : Delay about 1/25 sec
** : : : : ENDFOR
** : : : ENDIF
** : : : IF done = (music_time > piece_duration)
** : : : : #if CLI
** : : : : gi_quit = done
** : : : : #endif
** : : : : stop_all_notes()
** : : : : started = FALSE
** : : : ENDIF
** : : ENDWHILE
** : : #if not CLI
** : : set up Intuition signals
** : : wait for Intuition windowing signal
** : : interpret Intuition signal
** : : #endif
** : : continue
** : cleanup1:
** : #if not CLI
** : close_menu()
** : #endif
** : stop_all_notes()
** : Delay a little
** : remove_timer()
** : cleanup2:
** : #if not CLI
** : shut_window()
** : erase_recording()
** : #endif
** : stop_midi()
** : exit
** ENDROUTINE
*/
{
static FORM_TYPE form = {{180.0, -PID2, 200.0, -PID2},
{200.0, PID2, 180.0, PID2},
{170.0, -PID2, 165, -PID2},
{0.0, 0.0, 190.0, -PID2}};
auto char file_string[128];
auto int sts,
vox_index;
#ifndef CLI
static int code,
class, /* mouse codes esp. for menus */
main_mask,
signal;
auto struct IntuiMessage *message;
static struct Process *process;
static APTR old_error_window;
auto struct WBStartup *wb_msg;
auto struct WBArg *wb_arg;
auto BPTR olddir;
#endif
static int event_index = 0; /* Index into Events */
register int tick = 0; /* Index for counting 1/25's of a second*/
auto int tempo, /* ticks per second */
voice_index; /* index */
auto struct timeval tenth_second = {0, 1000000};
auto struct timeval music_time,
start_time, /* the current time in seconds */
piece_duration = {600, 0}, /* piece length */
sys_time;
static CHARACTER_TYPE cur_style;
static NOTE_LEN_TYPE note_len = {100, 2000, 2000};
static NOTE_EVENT_TYPE events[MAXVOICE]; /* currently playing notes */
#ifndef CLI
open_wind(); /* Open the window for AlgoRhythms */
#endif
open_midi_port(); /* Open the serial port for MIDI use */
if (gi_fubar)
{
/*
** Get out if serial port failed to open
*/
goto cleanup2; /* skip out on failure */
}
#ifndef CLI
init_menu(); /* Set up the menu */
tempo = 50 / delay_ticks; /* Tempo is pulses per second */
range = install_scale(1, scale); /* Install a musical scale */
half_range = range / 2; /* recalculate half_range */
process = (struct Process *)FindTask(NULL);
old_error_window = process->pr_WindowPtr;
process->pr_WindowPtr = (APTR)w;
open_orch_window();
open_forms_window();
open_voices_window();
open_colors_window();
init_rexx();
#endif
for (voice_index = 0; voice_index < MAXVOICE; voice_index++)
{
events[voice_index].nv_i_id = voice_index;
events[voice_index].nv_i_scale_index = half_range;
events[voice_index].nv_i_dynamic = 0.0;
events[voice_index].nv_r_start_time.tv_micro
= events[voice_index].nv_r_start_time.tv_secs
= events[voice_index].nv_r_stop_time.tv_micro
= events[voice_index].nv_r_stop_time.tv_secs
= events[voice_index].nv_r_duration.tv_micro
= events[voice_index].nv_r_duration.tv_secs
= events[voice_index].nv_i_channel
= 0;
events[voice_index].nv_i_cur_pitch = 60;
events[voice_index].nv_i_low_pitch = 24;
events[voice_index].nv_i_high_pitch = 108;
events[voice_index].nv_i_walking
= events[voice_index].nv_i_audio
= events[voice_index].nv_i_was_audio
= FALSE;
events[voice_index].nv_i_playing = FALSE;
events[voice_index].nv_i_audio_chan = C_NO_CHAN;
}
for (voice_index = 0; voice_index < 4; voice_index++)
{
events[voice_index].nv_i_walking
= events[voice_index].nv_i_audio
= events[voice_index].nv_i_was_audio
= TRUE;
}
init_audio();
/*
** set the form of the piece
*/
#ifdef CLI
if (argc != 2)
{
puts("usage: AlgoRhythmsCLI {Form_file}\n");
goto cleanup2;
}
strcpy(file_string, argv[1]);
sts = read_file(file_string, &piece_duration, &range, scale,
&num_voices, &tempo, &form, events,
¬e_len);
#else
if (0 == argc) /* if workbench passed an argument */
{
wb_msg = (struct WBStartup *)argv;
if (wb_msg->sm_NumArgs >= 2)
{
wb_arg = wb_msg->sm_ArgList;
wb_arg++;
if (wb_arg->wa_Lock != NULL)
{
olddir = CurrentDir(wb_arg->wa_Lock);
strcpy(file_string, wb_arg->wa_Name);
sts = read_file(file_string, &piece_duration, &range,
scale, &num_voices, &tempo, &form, events,
¬e_len);
CurrentDir(olddir);
}
else
{
strcpy(file_string, wb_arg->wa_Name);
sts = read_file(file_string, &piece_duration, &range,
scale, &num_voices, &tempo, &form, events,
¬e_len);
}
SetWindowTitles(w, file_string, (void *) -1L);
}
}
#endif
if (!sts)
{
if (0 == tempo)
{
delay_ticks = 0;
}
else
{
delay_ticks = 50 / tempo;
}
half_range = range / 2;
}
start_timer(); /* Start timer device */
GetSysTime(&sys_time); /* Get the time */
srand(sys_time.tv_micro); /* use microseconds time to seed random */
/*
** Save Time the program started
*/
start_time = sys_time;
music_time.tv_micro = music_time.tv_secs = 0; /*Clear time to start*/
#ifndef CLI
/*
** Initialize the Form parameters as random
*/
randomize_parameter(&form.frm_s_pitch);
randomize_parameter(&form.frm_s_rhythm);
randomize_parameter(&form.frm_s_dynamic);
randomize_parameter(&form.frm_s_texture);
draw_form(&piece_duration, &form);
set_form_gadgets(&form, &piece_duration, &tempo, events, ¬e_len,
num_voices);
set_voice_gadgets(events);
#endif
/*
** Initialize the ranges(pitch, dynamic, duration)
*/
ranges(&form, &music_time, &cur_style, ¬e_len);
#ifdef CLI
started = TRUE;
done = FALSE;
send_function(STARTFUNCT);
#endif
/*
** Master Control Loop
** Program stays in here until it gi_quits
*/
while ( !(gi_quit)) /* until we quit */
{
GetSysTime(&sys_time);
music_time = sys_time;
SubTime(&music_time, &start_time); /*music_time=cur_style - Start*/
/*
** as long as you are playing
*/
while (playing = ( !done && started) && !gi_quit)
{
/*
** get the time and convert
** to double normalize to
** the starting time of the
** piece of music
*/
GetSysTime(&sys_time);
music_time = sys_time;
SubTime(&music_time, &start_time);
/*
** Get the instantaneous mean & range of
** pitch, dynamics, duration, and texture
*/
if (-1 == CmpTime(&music_time, &range_time))
{
ranges(&form, &music_time, &cur_style, ¬e_len);
range_time = music_time;
AddTime(&range_time, &tenth_second);
}
/*
** scan the voices
*/
for (event_index = 0; event_index < MAXVOICE; event_index++)
{
/* if the note event is complete */
if (-1 == CmpTime(&music_time,
&events[event_index].nv_r_stop_time))
{
stop_note(&events[event_index], &music_time);
}
}
for (event_index = 0; event_index < num_voices; event_index++)
{
if (!(events[event_index].nv_i_playing))
{
make_event(&events[event_index], &cur_style,
&music_time, event_index, ¬e_len);
}
}
/*
** Indicate the time on the form graph
*/
#ifndef CLI
draw_time(&music_time, &piece_duration);
/* If there is an action, then find out what */
while (message = (struct IntuiMessage *) GetMsg(w->UserPort))
{
class = message->Class;
code = message->Code;
ReplyMsg((struct Message *)message);
if ((MENUPICK == class) && (MENUNULL == code))
{
continue;
}
parse_menu(class, code, &start_time, &form,
&piece_duration, &tempo, events, ¬e_len);
}
if (process_form_events(&form, &piece_duration,
&tempo, events, ¬e_len, &num_voices, scale, &range,
&half_range, &delay_ticks))
{
set_form_gadgets(&form, &piece_duration, &tempo, events,
¬e_len, num_voices);
}
process_color_events();
if (process_voice_events(events))
{
set_voice_gadgets(events);
}
process_audio_events();
switch (process_rexx(&form, &piece_duration, &tempo,
events, ¬e_len, &num_voices, scale, &range,
&half_range, &delay_ticks))
{
case C_REXX_CHANG_VOICE:
set_voice_gadgets(events);
break;
case C_REXX_CHANG_FORM:
set_form_gadgets(&form, &piece_duration, &tempo,
events, ¬e_len, num_voices);
break;
case C_REXX_CHANG_BOTH:
set_voice_gadgets(events);
set_form_gadgets(&form, &piece_duration, &tempo,
events, ¬e_len, num_voices);
break;
case C_REXX_PLAY:
started = TRUE;
done = FALSE;
GetSysTime(&start_time);
play_note_on(&reset_event);
for (vox_index = 0; vox_index < MAXVOICE; vox_index++)
{
events[vox_index].nv_r_start_time
= events[vox_index].nv_r_duration
= events[vox_index].nv_r_stop_time
= zero_time;
}
send_function(STARTFUNCT);
range_time = zero_time;
break;
case C_REXX_STOP:
started = FALSE;
for (vox_index = 0;
vox_index < MAXVOICE; vox_index++)
{
events[vox_index].nv_r_start_time
= events[vox_index].nv_r_duration
= events[vox_index].nv_r_stop_time
= zero_time;
}
stop_all_notes(events);
GetSysTime(&stop_time);
break;
case C_REXX_CONTINUE:
started = TRUE;
play_note_on(&reset_event);
GetSysTime(&sys_time);
start_time.tv_secs
+= (sys_time.tv_secs - stop_time.tv_secs);
break;
}
#endif
/*
** If delay_ticks is not zero then pause to
** simulate a regular beat
*/
if (delay_ticks != 0)
{
for (tick = 0; tick < delay_ticks; tick += 2)
{
send_function(CLOCKFUNCT); /* Send MIDI timing clock */
Delay(1); /* wait 1/50's of a sec*/
}
}
/*
** if the piece is done,
*/
if (done = (-1 == CmpTime(&music_time, &piece_duration)))
{
#ifdef CLI
gi_quit = done;
#endif
stop_all_notes(events);
started = FALSE;
}
}
/*
** if the piece is playing, then wait, free up AmigaDOS,
** until a mouse event hits this window
*/
#ifndef CLI
main_mask = 1L << w->UserPort->mp_SigBit;
signal
= Wait(main_mask | forms_mask | orch_mask
| voices_mask | rexx_mask | colors_mask);
/*
** When the event hits, find out what
*/
if (signal & main_mask)
{
while (message = (struct IntuiMessage *) GetMsg(w->UserPort))
{
class = message->Class;
code = message->Code;
ReplyMsg((struct Message *)message);
if ((MENUPICK == class) && (MENUNULL == code))
{
continue;
}
parse_menu(class, code, &start_time, &form, &piece_duration,
&tempo, events, ¬e_len);
}
}
if (signal & forms_mask)
{
if (process_form_events(&form, &piece_duration,
&tempo, events, ¬e_len, &num_voices, scale, &range,
&half_range, &delay_ticks))
{
set_form_gadgets(&form, &piece_duration, &tempo, events,
¬e_len, num_voices);
}
}
if (signal & voices_mask)
{
if (process_voice_events(events))
{
set_voice_gadgets(events);
}
}
if (signal & orch_mask)
{
process_audio_events();
}
if (signal & colors_mask)
{
process_color_events();
}
if (signal & rexx_mask)
{
switch (process_rexx(&form, &piece_duration, &tempo,
events, ¬e_len, &num_voices, scale, &range,
&half_range, &delay_ticks))
{
case C_REXX_CHANG_VOICE:
set_voice_gadgets(events);
break;
case C_REXX_CHANG_FORM:
set_form_gadgets(&form, &piece_duration, &tempo,
events, ¬e_len, num_voices);
break;
case C_REXX_CHANG_BOTH:
set_voice_gadgets(events);
set_form_gadgets(&form, &piece_duration, &tempo,
events, ¬e_len, num_voices);
break;
case C_REXX_PLAY:
started = TRUE;
done = FALSE;
GetSysTime(&start_time);
play_note_on(&reset_event);
for (vox_index = 0; vox_index < MAXVOICE; vox_index++)
{
events[vox_index].nv_r_start_time
= events[vox_index].nv_r_duration
= events[vox_index].nv_r_stop_time
= zero_time;
}
send_function(STARTFUNCT);
range_time = zero_time;
break;
case C_REXX_STOP:
started = FALSE;
for (vox_index = 0;
vox_index < MAXVOICE; vox_index++)
{
events[vox_index].nv_r_start_time
= events[vox_index].nv_r_duration
= events[vox_index].nv_r_stop_time
= zero_time;
}
stop_all_notes(events);
GetSysTime(&stop_time);
break;
case C_REXX_CONTINUE:
started = TRUE;
play_note_on(&reset_event);
GetSysTime(&sys_time);
start_time.tv_secs
+= (sys_time.tv_secs - stop_time.tv_secs);
break;
}
}
#endif
continue; /* continue in Master Control Loop until gi_quit */
}
cleanup1: /* normal dump out of the program */
#ifndef CLI
process->pr_WindowPtr = old_error_window;
close_colors();
close_voices();
close_forms();
close_orch();
shut_rexx();
#endif
stop_all_notes(events); /* turn off all the notes */
de_init_audio();
Delay(10); /* wait 1/5 seconds */
remove_timer(); /* release the timer device */
cleanup2: /* exit here if the serial port wouldn't open*/
#ifndef CLI
close_menu(); /* shut down menu */
shut_window(); /* remove the window */
erase_recording();
#endif
stop_midi(); /* close the serial device */
exit(EXIT_SUCCESS); /* exit program */
}
static void ranges( const FORM_TYPE *form,
const struct timeval *cur_time,
CHARACTER_TYPE *next_now, NOTE_LEN_TYPE *note_len)
/*
** FUNCTIONAL DESCRIPTION:
** Calculates the current ranges for pitch, dynamic, duration, and the
** number of voices playing.
**
** ARGUMENTS:
**
** form-
** description: period and phase of mean and range
** for pitch,dyn,text,rhythm
** data_type: pointer to FORM_TYPE
** access: read only
**
** cur_time-
** description: period and phase of mean and range
** data_type: pointer to timeval
** access: read only
**
** next_now-
** description: period and phase of mean and range
** data_type: pointer to CHARACTER_TYPE
** access: write only
**
** DESIGN:
** ROUTINE ranges
** : phase = TWOPI * (CurrentTime->tv_secs
** + (CurrentTime->tv_micro * 1E-6))
** : next_now->ch_i_pitch_range = (sin(phase / pitch->prm_d_range_cycle
** + pitch->prm_d_range_phase) + 1) * realhalf_range
** : next_now->ch_i_pitch_mean = (sin(phase / pitch->prm_d_mean_cycle
** + pitch->prm_d_mean_phase) + 1.0) * realhalf_range
** : IF pitch_mean and range could create negative note numbers
** : : center up range smaller
** : ENDIF
** : ELSE
** : : IF pitch_mean and range could create scale notes beyond the scale
** : : : center up range smaller
** : : ENDIF
** : ENDIF
** : next_now->ch_i_dynamic_range= (sin(phase / dynamic->prm_d_range_cycle
** + dynamic->prm_d_range_phase) + 1.01) * 63.0
** : next_now->ch_i_dynamic_mean = (sin(phase / dynamic->prm_d_mean_cycle
** + dynamic->prm_d_mean_phase) + 1.01) * 50.0 + 25.0
** : next_now->ch_i_dur_range = (sin(phase / duration->prm_d_range_cycle
** + duration->prm_d_range_phase) + 1.01) / 2.0 * dif_note_len_ms
** : next_now->ch_i_dur_mean = (sin(phase / duration->prm_d_mean_cycle
** + duration->prm_d_mean_phase) + 1.01) / 2.0 * dif_note_len_ms
** + min_note_len_ms
** : next_now->ch_i_thickness = (sin(phase / thickness->prm_d_range_cycle
** + thickness->prm_d_range_phase) + 1) / 2
** * (num_voices - 1)
** ENDROUTINE
*/
{
auto double realrange, /* floating-point version of range */
realhalf_range, /* floating-point version of half_range */
phase;
realrange = (double) range;
realhalf_range = (double) half_range;
phase = TWOPI * ((double)(cur_time->tv_secs) +
((double)(cur_time->tv_micro) * 1E-6));
next_now->ch_i_pitch_range
= (int)floor(((sin((phase / form->frm_s_pitch.prm_d_range_cycle)
+ form->frm_s_pitch.prm_d_range_phase) + 1.0) * realhalf_range));
/* pitch range is a sin function of time*/
next_now->ch_i_pitch_mean
= (int)floor(((sin((phase / form->frm_s_pitch.prm_d_mean_cycle)
+ form->frm_s_pitch.prm_d_mean_phase) + 1.0) * realhalf_range));
/*
** The following bounds checking prevents pitch range from
** overlapping into non-usable notes (less than 0 and higher
** than the top scale note). It forces the range to be
** from top or bottom of the scale through the mean and
** an equal distance to the other side
*/
if ((next_now->ch_i_pitch_mean - (next_now->ch_i_pitch_range / 2)) < 0)
{
next_now->ch_i_pitch_mean = 1 + ((next_now->ch_i_pitch_mean
+ (next_now->ch_i_pitch_range / 2)) / 2);
}
else
{
if ((next_now->ch_i_pitch_mean + (next_now->ch_i_pitch_range / 2))
> (double)range)
{
next_now->ch_i_pitch_mean = realhalf_range - 1
+ ((next_now->ch_i_pitch_mean
- (next_now->ch_i_pitch_range / 2)) / 2);
}
}
next_now->ch_i_dynamic_range
= (int)floor(((
sin((phase / (form->frm_s_dynamic.prm_d_range_cycle))
+ form->frm_s_dynamic.prm_d_range_phase) + 1.01) * 63.0));
/* Range of dynamics is a sin function of time */
next_now->ch_i_dynamic_mean
= (int)floor(((sin(phase / form->frm_s_dynamic.prm_d_mean_cycle
+ form->frm_s_dynamic.prm_d_mean_phase) + 1.01) * 50.0 + 25.0));
/* Median dynamic is a sin function of time */
next_now->ch_i_dur_range
= (int)floor((((sin(phase / form->frm_s_rhythm.prm_d_range_cycle
+ form->frm_s_rhythm.prm_d_range_phase) + 1.01) / 2.0)
* (double)note_len->len_i_dif));
/*
** Range of durations is a sin function of time
*/
next_now->ch_i_dur_mean
= (int)floor(((sin(phase / form->frm_s_rhythm.prm_d_mean_cycle
+ form->frm_s_rhythm.prm_d_mean_phase) + 1.01) / 2.0
* (double)note_len->len_i_dif)) + note_len->len_i_min;
/* Median duration is a sin function of time */
next_now->ch_i_thickness
= (int)floor((((sin((phase / form->frm_s_texture.prm_d_range_cycle)
+ form->frm_s_texture.prm_d_range_phase) + 1.0) / 2.0
* (double)(num_voices - 1))) + 0.5);
/* the number of voices playing is a sin function of time */
return;
}
void randomize_parameter(PARAMETER_TYPE *param)
/*
** FUNCTIONAL DESCRIPTION:
** Calculate random values for period/phase of mean/range for parameter
**
** RETURN VALUE:
** description: randomized period/phase of mean/range
** data_type: PARAMETER_TYPE
**
** DESIGN:
** ROUTINE randomize_parameter()
** : param->prm_d_mean_cycle = rand() / realrand_max * 120 + 90
** : param->prm_d_mean_phase = rand() / realrand_max * TWOPI
** : param->prm_d_range_cycle = rand() / realrand_max * 120 + 90
** : param->prm_d_range_phase = rand() / realrand_max * TWOPI
** ENDROUTINE
*/
{
/*
** Returns a randomized parameter of any type
** Periods are 1.5 to 3.5 minutes
*/
register double realrand_max;
realrand_max = (double)RAND_MAX;
param->prm_d_mean_cycle
= ((double)rand() / realrand_max) * 120.0 + 90.0;
param->prm_d_mean_phase
= ((double)rand() / realrand_max) * TWOPI - PI;
param->prm_d_range_cycle
= ((double)rand() / realrand_max) * 120.0 + 90.0;
param->prm_d_range_phase
= ((double)rand() / realrand_max) * TWOPI - PI;
return;
}
static void make_event(NOTE_EVENT_TYPE *new_event, CHARACTER_TYPE *style,
struct timeval *music_time, const int event_index,
NOTE_LEN_TYPE *note_len)
/*
** FUNCTIONAL DESCRIPTION:
** Calculate and play a note
**
** ARGUMENTS:
**
** new_event-
** description: One voice's event structure
** data_type: pointer to NOTE_EVENT_TYPE
** access: read/write
**
** style-
** description: current ranges of pitch/dynamic/duration/texture
** data_type: pointer to CHARACTER_TYPE
** access: read only
**
** music_time-
** description: relative time since starting playing music
** data_type: pointer to timeval
** access: read only
**
** event_index-
** description: number of voice
** data_type: int
** access: read only
**
** DESIGN
/*
** ROUTINE make_event()
** : IF voice is playing (won't happen any more)
** : : turn off the note
** : : #if not CLI
** : : IF recording
** : : : record note off
** : : ENDIF
** : : #endif
** : ENDIF
** : IF this voice is higher than the current number of voices playing
** : : return
** : ENDIF
** : IF dynamic_range > 1
** : : new_dynamic = (rand() % style->ch_i_dynamic_range)
** - (style->ch_i_dynamic_range / 2) + style->ch_i_dynamic_mean
** : ELSE
** : : new_dynamic = style->ch_i_dynamic_mean
** : ENDIF
** : check bounds on new_dynamic and fold in if necessary
** : new_event->nv_i_dynamic = new_dynamic
** : IF duration range is > 1 ms
** : : new_duration = (rand() % style->ch_i_dur_range
** - style->ch_i_dur_range / 2 + style->ch_i_dur_mean)
** : ELSE
** : : new_duration = style->ch_i_dur_mean
** : ENDIF
** : IF new_duration is shorter than min_note_len_ms
** : : force new_duration = min_note_len_ms
** : ENDIF
** : IF new_duration is longer than max_note_len_ms
** : : force new_duration = max_note_len_ms
** : ENDIF
** : convert new_duration from millisecs to sys time in new_duration_time
** : copy new_duration_time to new_event->nr_r_duration
** : new_event->nv_r_stop_time = music_time + duration
** : IF voice is walking
** : : high_note = current pitch is >= highest pitch allowed in voice
** : : low_note = current pitch is <= lowest pitch allowed in voice
** : : walk = rand() % 3 - 1
** : : IF low_note
** : : : walk = 1
** : : ENDIF
** : : IF high_note
** : : : walk = -1
** : : ENDIF
** : : new_pitch_index = new_event->nv_i_scale_index + walk
** : ELSE
** : : IF pitch_range > 0
** : : : new_pitch_index = rand() % style->ch_i_pitch_range
** - style->ch_i_pitch_range / 2 + style->ch_i_pitch_mean
** : : ELSE
** : : : new_pitch_index = style->ch_i_pitch_mean
** : : ENDIF
** : : high_note = pitch > highest pitch allowed in voice
** : : low_note = pitch < lowest pitch allowed in voice
** : : ok_rand_note = not high_note or low_note
** : ENDIF
** : IF pitch index >= range
** : : new_pitch_index = range -1 fold under if too high
** : ENDIF
** : IF new_pitch_index < 0
** : : new_pitch_index = 0
** : ENDIF
** : new_event->nv_i_scale_index = new_pitch_index
** : new_event->nv_i_scale_index = scale[new_pitch_index]
** : IF (playing AND ok_rand_note)
** : : play_note_on(new_event)
** : : set playing for voice
** : : #if not CLI
** : : IF recording
** : : : record_note_event(new_event)
** : : ENDIF
** : ENDIF
** ENDROUTINE
*/
{
auto int low_note, /* Boolean flag that the note is as the */
/* bottom of the scale */
high_note, /* Boolean flag = note is at top of scale*/
ok_rand_note = TRUE, /* not low and not high */
walk, /* a direction for the note to walk */
new_pitch_index, /* index to the new pitch */
new_dynamic, /* temp new dynamic value */
new_duration; /* a temporary new duration value */
auto struct timeval new_duration_time;
if (new_event->nv_i_playing)
{
new_event->nv_i_dynamic = 0;
new_event->nv_r_stop_time = *music_time;
if (new_event->nv_i_was_audio)
{
play_audio_note(new_event);
}
else
{
play_note_on(new_event); /* Turn off the old note */
}
new_event->nv_i_playing = FALSE;
#ifndef CLI
if (recording)
{
record_note_event(new_event);
}
#endif
}
if (event_index > style->ch_i_thickness)
{
return;
}
if (style->ch_i_dynamic_range > 1)
{
new_dynamic = (rand() % style->ch_i_dynamic_range)
- (style->ch_i_dynamic_range / 2)
+ style->ch_i_dynamic_mean;
}
else
{
new_dynamic = style->ch_i_dynamic_mean;
}
/*
** Check boundaries on dynamic
*/
if (new_dynamic > 127)
{
new_dynamic = 127;
}
if (new_dynamic < 30 )
{
new_dynamic = 30;
}
new_event->nv_i_dynamic = new_dynamic; /* make dynamic a byte*/
if (style->ch_i_dur_range > 1)
{
new_duration = (rand() % style->ch_i_dur_range
- style->ch_i_dur_range / 2
+ style->ch_i_dur_mean);
}
else
{
new_duration = style->ch_i_dur_mean;
}
new_duration =
((new_duration < note_len->len_i_min)
? note_len->len_i_min : new_duration);
new_duration =
((new_duration > note_len->len_i_max)
? note_len->len_i_max : new_duration);
new_duration_time.tv_secs = new_duration / 1000;
new_duration_time.tv_micro = (new_duration * 1000) % 1000000;
/*
** Put duration in durations list
*/
new_event->nv_r_duration = new_duration_time;
new_event->nv_r_start_time = *music_time;
new_event->nv_r_stop_time = new_duration_time;
AddTime(&new_event->nv_r_stop_time, music_time);
/*
** If the voice is walking
*/
if (new_event->nv_i_walking)
{
high_note
= new_event->nv_i_cur_pitch >= new_event->nv_i_high_pitch;
low_note = new_event->nv_i_cur_pitch <= new_event->nv_i_low_pitch;
walk = (rand() % 3) - 1;
if (low_note)
{
walk = 1;
}
if (high_note)
{
walk = -1;
}
new_pitch_index = new_event->nv_i_scale_index + walk;
}
else
{
if (style->ch_i_pitch_range > 0)
{
new_pitch_index = rand() % style->ch_i_pitch_range
- style->ch_i_pitch_range / 2
+ style->ch_i_pitch_mean;
}
else
{
new_pitch_index = style->ch_i_pitch_mean;
}
high_note = (scale[new_pitch_index] >= new_event->nv_i_high_pitch);
low_note = (scale[new_pitch_index] <= new_event->nv_i_low_pitch);
ok_rand_note = !(high_note || low_note);
}
if (new_pitch_index >= range)
{
new_pitch_index = range - 1; /* fold under if too high */
}
if (new_pitch_index < 0)
{
new_pitch_index = 0; /* fold up if too low */
}
new_event->nv_i_scale_index = new_pitch_index;
new_event->nv_i_cur_pitch = scale[new_pitch_index];
/*
** Play the note
*/
if (playing && ok_rand_note)
{
if (new_event->nv_i_audio)
{
play_audio_note(new_event);
}
else
{
play_note_on(new_event);
}
new_event->nv_i_playing = TRUE;
#ifndef CLI
if (recording)
{
record_note_event(new_event);
}
#endif
}
return;
}
#ifndef CLI
static void parse_menu(const int class, const int code,
struct timeval *str_time, FORM_TYPE *form,
struct timeval *duration, int *tempo,
NOTE_EVENT_TYPE *events, NOTE_LEN_TYPE *note_len)
/*
** FUNCTIONAL DESCRIPTION:
** Interpret Intuition mouse/menu operations
**
** ARGUMENTS:
**
** class-
** description: Intuition event class
** data_type: int
** access: read only
**
** code-
** description: Intuition event code
** data_type: int
** access: read only
**
** str_time-
** description: the real clock time that the piece started playing
** data_type: pointer to struct timeval
** access: write only
**
** form-
** description: period/phase of mean/range for pitch/dyn/rhyth/text
** data_type: pointer to FORM_TYPE
** access: read/write
**
** duration-
** description: Length of piece
** data_type: pointer to struct timeval
** access: read/write
**
** tempo-
** description: beats per second
** data_type: pointer to int
** access: read/write
**
** events-
** description:
** data_type: pointer to NOTE_EVENT_TYPE
** access: read/write
**
** DESIGN:
**
** ROUTINE parse_menu
** : init file_req
** : CASE class
** : : NEWSIZE
** : : : draw_form()
** : : MENUPICK
** : : : IF code != MENUNULL
** : : : : item = ITEMNUM(code)
** : : : : subitem = SUBNUM(code)
** : : : : CASE MENUNUM(code)
** : : : : : PROJECT:
** : : : : : : IF item != NOITEM
** : : : : : : : CASE item
** : : : : : : : : 0
** : : : : : : : : : set gi_quit
** : : : : : : : : 1
** : : : : : : : : : response = AutoRequest thanks
** : : : : : : : : 2
** : : : : : : : : : IF AslBase != NULL
** : : : : : : : : : : use file requester to get MIDI file name
** : : : : : : : : 3
** : : : : : : : : : erase_recording
** : : : : : : : : 4
** : : : : : : : : : record_init
** : : : : : : : : : toggle recording
** : : : : : : : : 5
** : : : : : : : : : use file requester to save form file
** : : : : : : : : 6
** : : : : : : : : : use file requester to load form file
** : : : : : : : : 7
** : : : : : : : : : continue playing
** : : : : : : : : 8
** : : : : : : : : : stop playing
** : : : : : : : : 9
** : : : : : : : : : Start playing music
** : : : : : : : FORM
** : : CLOSEWINDOW
** : : : gi_quit = TRUE
** ENDROUTINE
*/
{
auto int asl_result,
vox_index,
sts, /* file status */
item = 0, /* menu item */
subitem = 0, /* menu subitem */
response;
static struct FileRequester *asl_request;
static struct TagItem asl_tags[8] =
{{ASL_Dir, NULL}, {ASL_File, NULL}, {ASL_Hail, NULL},
{ASL_Window, NULL}, {ASL_LeftEdge, 10}, {ASL_TopEdge, 10},
{ASL_Height, 180}, {TAG_DONE, NULL}};
#ifdef MEASURE
static unsigned char measure_string[32];
static struct IntuiText measure_txt
= {2, 1, JAM2, 15, 10, &font_choice, measure_string, NULL};
#endif
const struct timeval zero_time = {0, 0};
static struct timeval sys_time;
static struct ReqFileRequester file_req;
static char midi_path[96] = "\0",
midi_dir[96] = "\0",
midi_file[64] = "\0",
form_path[64] = "\0",
form_dir[96] = "\0",
form_file[64] = "\0",
title_string[128] = "\0";
/*
** Strings for gadgets
*/
static char load_file_banner[] = "Load Form File",
save_form_banner[] = "Save Form File",
midi_form_banner[] = "Save MIDI file",
about1_str[32] = "Welcome to AlgoRhythms 3.1",
about2_str[40] = "Copyright 1994 Thomas E. Janzen",
thanks_str[7] = "Thanks";
static struct IntuiText
about1_txt = {2, 1, JAM2, 5, 4, &font_choice, about1_str, NULL},
about2_txt = {2, 1, JAM2, 5, 15, NULL, about2_str, &about1_txt},
thanks_txt = {2, 1, JAM1, 5, 4, NULL, thanks_str, NULL};
file_req.dirnamescolor = 2;
file_req.devicenamescolor = 2;
switch (class)
{
case NEWSIZE: /* Window has been re-sized, so re-draw the graph */
draw_form(duration, form);
break;
case MENUPICK: /* a menu selection was made */
if (code != MENUNULL)
{
item = ITEMNUM(code);
subitem = SUBNUM(code);
switch (MENUNUM(code))
{
case PROJECT: /* Project menu strip was selected */
if (item != NOITEM)
switch (item)
{
case 9: /* gi_quit program */
gi_quit = TRUE;
break;
case 8: /* About copyright notice */
response
= AutoRequest(w, &about2_txt, &thanks_txt,
&thanks_txt, 0L, 0L, 300L, 60L);
break;
case 7: /* Save MIDI file */
asl_tags[0].ti_Data = (ULONG)midi_dir;
asl_tags[1].ti_Data = (ULONG)midi_file;
asl_tags[2].ti_Data
= (ULONG)midi_form_banner;
asl_tags[3].ti_Data = (ULONG)w;
asl_request
= AllocAslRequest(ASL_FileRequest,
asl_tags);
asl_result = AslRequest(asl_request, NULL);
if (NULL == asl_result)
{
DisplayBeep(NULL);
break;
}
strcpy(midi_dir, asl_request->rf_Dir);
strcpy(midi_file, asl_request->rf_File);
strcpy(midi_path, asl_request->rf_Dir);
if ( (midi_path[strlen(midi_path) - 1] != ':')
&& (strlen(midi_path) != 0))
{
strcat(midi_path, "/");
}
strcat(midi_path, asl_request->rf_File);
write_midi(midi_path);
FreeAslRequest(asl_request);
break;
case 6: /* Erase */
erase_recording();
break;
case 5: /* Record */
recording = record_init();
break;
case 4: /* Save a form file */
asl_tags[0].ti_Data = (ULONG)form_dir;
asl_tags[1].ti_Data = (ULONG)form_file;
asl_tags[2].ti_Data
= (ULONG)save_form_banner;
asl_tags[3].ti_Data = (ULONG)w;
asl_request
= AllocAslRequest(ASL_FileRequest,
asl_tags);
asl_result = AslRequest(asl_request, NULL);
if (NULL == asl_result)
{
DisplayBeep(NULL);
break;
}
strcpy(form_dir, asl_request->rf_Dir);
strcpy(form_file, asl_request->rf_File);
strcpy(form_path, asl_request->rf_Dir);
if ((form_path[strlen(form_path) - 1] != ':')
&& (strlen(form_path) != 0))
{
strcat(form_path, "/");
}
strcat(form_path, asl_request->rf_File);
sts = save_file(form_path, duration, &range,
scale, &num_voices, tempo,
form, events, note_len);
if (sts)
{
DisplayBeep(NULL);
break;
}
strcpy(title_string, form_path);
SetWindowTitles(w, title_string, (void *) -1L);
FreeAslRequest(asl_request);
break;
case 3: /* load a form file */
asl_tags[0].ti_Data = (ULONG)form_dir;
asl_tags[1].ti_Data = (ULONG)form_file;
asl_tags[2].ti_Data
= (ULONG)load_file_banner;
asl_tags[3].ti_Data = (ULONG)w;
asl_request
= AllocAslRequest(ASL_FileRequest,
asl_tags);
asl_result = AslRequest(asl_request, NULL);
if (NULL == asl_result)
{
DisplayBeep(NULL);
break;
}
strcpy(form_dir, asl_request->rf_Dir);
strcpy(form_file, asl_request->rf_File);
strcpy(form_path, asl_request->rf_Dir);
if ((form_path[strlen(form_path)- 1] != ':')
&& (strlen(form_path) != 0))
{
strcat(form_path, "/");
}
strcat(form_path, asl_request->rf_File);
sts = read_file(form_path,duration, &range,
scale, &num_voices, tempo,
form, events, note_len);
if (!sts)
{
if (0 == *tempo)
{
delay_ticks = 0;
}
else
{
delay_ticks = 50 / (*tempo);
}
half_range = range / 2;
draw_form(duration, form);
strcpy(title_string, form_path);
SetWindowTitles(w, title_string,
(void *) -1L);
range_time = zero_time;
set_form_gadgets(form, duration,
tempo, events, note_len, num_voices);
set_voice_gadgets(events);
}
else
{
DisplayBeep(NULL);
}
FreeAslRequest(asl_request);
break;
case 2: /* Continue after stopping */
started = TRUE;
play_note_on(&reset_event);
GetSysTime(&sys_time);
str_time->tv_secs = str_time->tv_secs
+ (sys_time.tv_secs - stop_time.tv_secs);
break;
case 1: /* stop the music but don't exit */
started = FALSE;
for (vox_index = 0;
vox_index < MAXVOICE; vox_index++)
{
events[vox_index].nv_r_start_time
= events[vox_index].nv_r_duration
= events[vox_index].nv_r_stop_time
= zero_time;
}
stop_all_notes(events);
GetSysTime(&stop_time);
#ifdef MEASURE
sprintf(measure_string,"%d", gi_notes_measure);
PrintIText(rast_port, &measure_txt, 1, 1);
gi_notes_measure = 0;
#endif
break;
case 0: /* Start to Play music */
started = TRUE;
done = FALSE;
GetSysTime(str_time);
play_note_on(&reset_event);
for (vox_index = 0;
vox_index < MAXVOICE; vox_index++)
{
events[vox_index].nv_r_start_time
= events[vox_index].nv_r_duration
= events[vox_index].nv_r_stop_time
= zero_time;
}
send_function(STARTFUNCT);
range_time = zero_time;
break;
default:
break;
} /*switch itemnum*/
break;
case FORM: /* Form menu strip was selected */
if (item != NOITEM)
switch (item)
{
case 0: /* ReDraw */
draw_form(duration, form);
break;
case 1: /* Form */
open_forms_window();
break;
case 2: /* Voice */
open_voices_window();
break;
case 3: /* Orchestra */
open_orch_window();
break;
case 4: /* Colors */
open_colors_window();
break;
default:
break;
} /* switch itemnum */
break;
} /* switch menunum */
} /* while not menunull */
break;
case CLOSEWINDOW:
gi_quit = TRUE;
break;
default:
break;
} /* switch class */
return;
} /* end function */
#endif
static double cvt_time_secs(struct timeval *Time)
/*
** FUNCTIONAL DESCRIPTION:
** Converts system time into floating seconds
**
** RETURN VALUE:
** description: floating point seconds
** data_type: double
**
** ARGUMENTS:
**
** Time-
** description: time
** data_type: pointer to struct timeval
** access: read only
**
** DESIGN:
**
** ROUTINE cvt_time_secs
** : return Time->tv_secs + Time->tv_micro / 1E6
** ENDROUTINE
*/
{
return (double)Time->tv_secs + ((double)Time->tv_micro / 1E6);
}
static void stop_note( NOTE_EVENT_TYPE *new_event,
struct timeval *music_time)
/*
** FUNCTIONAL DESCRIPTION:
** Turn off a note
**
** ARGUMENTS:
**
** new_event-
** description: a voice
** data_type: pointer to NOTE_EVENT_TYPE
** access: read/write
**
** music_time-
** description: Current relative time since starting to play
** data_type: pointer to struct timeval
** access: read only
**
** DESIGN
**
** ROUTINE stop_note
** : IF note is playing
** : : clear dynamic
** : : set stop time to music time
** : : play_note_on(new_event)
** : : clear playing flag
** #if not CLI
** : : IF recording
** : : : record_note_event(new_event)
** : : ENDIF
** : ENDIF
** ENDROUTINE
*/
{
if (new_event->nv_i_playing)
{
new_event->nv_i_dynamic = 0;
new_event->nv_r_stop_time = *music_time;
if (new_event->nv_i_was_audio)
{
play_audio_note(new_event);
}
else
{
play_note_on(new_event); /* Turn off the old note */
}
new_event->nv_i_playing = FALSE;
#ifndef CLI
if (recording)
{
record_note_event(new_event);
}
#endif
}
return;
}